import datetime
import json
import logging
import re
from concurrent.futures import ThreadPoolExecutor
from typing import *
from urllib.parse import unquote, urljoin

import lxml.etree
import lxml.html
from requests import Session
from requests.adapters import HTTPAdapter

import utils
from settings import default_settings as Settings


class User:
    def __init__(self, user) -> None:
        ''' {
            "last_sign": "", // keep empty
            "login": {
                "username": "", // username or email
                "password": "", / password
                "loginsubmit": "yes",
                "loginfield": "auto",
                "questionid": 0, // keep empty unless you've configed your question
                "answer": "" // keep empty unless you've configed your question
            },
            "cookies": []
        }
        '''
        self.user = user
        self.uid = '0'
        self.username = ''
        self.formhash = ''
        self.cookiepre = ''
        self.sess = Session()
        self.sess.headers.update({'User-Agent': utils.user_agent})
        self.sess.mount('http://',
                        HTTPAdapter(pool_connections=40, pool_maxsize=40))
        self.sess.mount('https://',
                        HTTPAdapter(pool_connections=40, pool_maxsize=40))
        for cookie in self.user['cookies']:
            self.sess.cookies.set(**cookie)

    def login(self) -> bool:
        try:
            r = self.sess.get(Settings.api,
                              params=Settings.login_params).json()
            if 'Message' not in r or r['Message'].get(
                    'messageval') != 'login_succeed':
                if not self.user['login']:
                    raise Exception('无效的登录信息')
                r = self.sess.post(Settings.api,
                                   params=Settings.login_params,
                                   data=self.user['login']).json()
                if r['Message'].get('messageval') != 'login_succeed':
                    return False
                else:
                    r = self.sess.get(Settings.api,
                                    params=Settings.login_params).json()
            logging.info(r['Variables'])
            self.uid = r['Variables']['member_uid']
            self.username = r['Variables']['member_username']
            self.formhash = r['Variables']['formhash']
            self.cookiepre = r['Variables']['cookiepre']
            for cookie in self.sess.cookies:
                existed_cookie = None
                for c in self.user['cookies']:
                    if c.get('name') == cookie.name:
                        existed_cookie = c
                        c['value'] = cookie.value
                        c['domain'] = cookie.domain
                        c['path'] = cookie.path
                if existed_cookie is None:
                    self.user['cookies'].append({
                        'name': cookie.name,
                        'value': cookie.value,
                        'domain': cookie.domain,
                        'path': cookie.path,
                    })
            logging.info(r['Message']['messagestr'])
            return True
        except KeyError:
            raise
        except Exception as e:
            logging.error(e)
            return False

    def sign(self):
        n = len(self.cookiepre)
        uids = [23, 26, 31, 37, 286, 368, 423, 523, 595, 2539, 1682]
        notice_keys = ['', '威望', '金钱', '流通券', '', '南海纺织B轮', '南海纺织', '战争公债', '']
        for uid in uids:
            url = urljoin(Settings.server, f'space-uid-{uid}.html')
            r = self.sess.get(url)
            cookies = dict()
            for c in r.cookies:
                if c.name.startswith(self.cookiepre) and c.value is not None:
                    cookies[c.name[n:]] = unquote(c.value)
            if 'creditnotice' not in cookies or 'creditrule' not in cookies:
                logging.info(f'cookies: {cookies}')
                continue
            try:
                notice = list(map(int, cookies['creditnotice'].split('D')))
                message = cookies['creditrule'] + '\t' + ', '.join(
                    f'{notice_keys[i]}+{notice[i]}'
                    for i in range(1, 8) if notice_keys[i] and notice[i])
                logging.info(message)
            except Exception as e:
                logging.error(f'访问{uid}出错', e)
        self.user['last_sign'] = datetime.date.today().strftime(
            utils.date_format)

    @property
    def logined(self) -> bool:
        return int(self.uid) != 0

    @property
    def signed(self) -> bool:
        return datetime.date.today().strftime(
            utils.date_format) == self.user['last_sign']

    def get_lands(self) -> List[lxml.html.HtmlElement]:
        # https://lgqm.gq/plugin.php?id=gfarm:front&act=ajax&uid=121&inajax=1&ajaxtarget=ptable
        r = self.sess.get(Settings.plugin,
                          params={
                              'id': 'gfarm:front',
                              'act': 'ajax',
                              'uid': self.uid,
                              'inajax': '1',
                              'ajaxtarget': 'ptable',
                          },
                          headers={'x-requested-with': 'XMLHttpRequest'})
        root: lxml.etree.ElementBase = lxml.etree.fromstring(r.content)
        div: lxml.html.HtmlElement = lxml.html.fragment_fromstring(
            root.text, create_parent='div')
        return div.xpath('/div/table/tr/td/div[@class="titlediv"]/parent::*')

    def sell_all_unlock(self) -> int:
        # https://lgqm.gq/plugin.php?id=gfarm:front&mod=depot&act=allsale&do=12&formhash=xxxxxxxx&inajax=1&ajaxtarget=outdepots
        r = self.sess.get(Settings.plugin,
                          params={
                              'id': 'gfarm:front',
                              'mod': 'depot',
                              'act': 'allsale',
                              'do': '12',
                              'formhash': self.formhash,
                              'inajax': '1',
                              'ajaxtarget': 'outdepots',
                          },
                          headers={'x-requested-with': 'XMLHttpRequest'})
        m = re.search(r'成功卖出获得(?P<money>\d+)南海金钱\d+南海元宝', r.text, re.M)
        if m is None:
            return 0
        logging.info(m.group())
        return int(m.group('money'))

    def get_seeds(self):
        # https://lgqm.gq/plugin.php?id=gfarm:front&mod=depot&act=ajax&do=9&inajax=1&ajaxtarget=outdepots
        r = self.sess.get(Settings.plugin,
                          params={
                              'id': 'gfarm:front',
                              'mod': 'depot',
                              'act': 'ajax',
                              'do': '9',
                              'inajax': '1',
                              'ajaxtarget': 'outdepots'
                          },
                          headers={'x-requested-with': 'XMLHttpRequest'})
        root: lxml.etree.ElementBase = lxml.etree.fromstring(r.content)
        div: lxml.html.HtmlElement = lxml.html.fragment_fromstring(
            root.text, create_parent='div')
        seeds: List[Tuple[int, str]] = list()
        for seed in div.xpath('/div/div[@id="indepots"]/ul/li'):
            if seed.find(
                    './div/div[@class="showattr"]/div[@class="showborder"]/div[@id]/div[@class="bottomlevel"]'
            ).text.strip() != '20级':
                continue
            sid: str = seed.find(
                './div[1]/div[@class="showattr"]/div[@class="showborder"]/div[@id][1]'
            ).attrib['id']
            snum = int(
                seed.find(
                    './div[1]/div[@class="showattr"]/div[@class="depotnum"]').
                text)
            seeds.append((snum, sid))
        if not seeds:
            seeds.append((0, ''))
        return seeds

    def buy_seed(self, gid: int = 63, gnum: int = 999) -> bool:
        r = self.sess.post(Settings.plugin,
                           params={
                               'id': 'gfarm:front',
                               'mod': 'shop',
                               'act': 'buy',
                               'formhash': self.formhash,
                           },
                           data={
                               'goodid': gid,
                               'do': '9',
                               'dnum': gnum,
                           })
        m = re.search(r'成功购买\S{1,5}种子\d+个', r.text, re.M)
        if m is not None:
            logging.info(m.group())
        return m is not None

    def plant(self, formhash: str, land_id: str, seed_id: str,
              crop_state: bool):
        germ, crop = 0, 0
        while True:
            if crop_state:
                r = self.sess.get(
                    Settings.plugin,
                    params={
                        'id': 'gfarm:front',
                        'mod': 'gfarm_ajax',
                        'formhash': formhash,
                        'act': 'getcrop',
                        'landid': land_id,
                        'inajax': '1',
                        'ajaxtarget': ''
                    },
                    headers={'x-requested-with': 'XMLHttpRequest'})
                crop += 1
            else:
                r = self.sess.get(
                    Settings.plugin,
                    params={
                        'id': 'gfarm:front',
                        'mod': 'gfarm_ajax',
                        'formhash': formhash,
                        'act': 'germajax',
                        'depotid': seed_id,
                        'landid': land_id,
                        'inajax': '1',
                        'ajaxtarget': ''
                    },
                    headers={'x-requested-with': 'XMLHttpRequest'})
                germ += 1
            if re.search(r'非法字符', r.text, re.M):
                logging.error('访问请求当中含有非法字符')
                break
            if re.search(r'体力值不足', r.text, re.M):
                if crop_state:
                    crop -= 1
                else:
                    germ -= 1
                logging.info(f'{land_id} 体力不足 - germ: {germ}, crop: {crop}')
                break
            crop_state = not crop_state

    def farm(self):
        # 丢弃第一次请求的内容(因为农场的数据可能还未更新)
        self.sess.get(Settings.plugin, params={
            'id': 'gfarm:front',
        })
        r = self.sess.get(Settings.plugin, params={
            'id': 'gfarm:front',
        })
        html: lxml.html.HtmlElement = lxml.html.fromstring(r.content)
        span: lxml.html.HtmlElement = html.find('.//span[@id="userpower"]')
        power = int(span.text.strip())
        if power < Settings.limit_strength:
            logging.info(f'体力只有{power}(<{Settings.limit_strength})，不进行农场种植')
            return
        logging.info(f'当前体力有{power}')

        snum, sid = max(self.get_seeds())
        if snum < Settings.limit_snum:
            if not self.buy_seed(gnum=999 - snum):
                logging.info('购买种子的金钱不足')
                # if input('金钱不足，是否出售作物？[Y]/N').strip().lower().startswith('n'):
                #     logging.info('Bye......')
                #     return
                self.sell_all_unlock()
                if not self.buy_seed(gnum=999 - snum):
                    logging.error('出售之后金钱仍然不足')
                    return
            snum, sid = max(self.get_seeds())

        lands = self.get_lands()
        with ThreadPoolExecutor(len(lands)) as pool:
            for land in lands:
                land_id = land.find('./span[1]').attrib['id']
                crop_state = land.find(
                    './span/img[@class="cropimg"]') is not None
                pool.submit(self.plant, self.formhash, land_id, sid,
                            crop_state)
